#ifndef _M_ROOM_H__
#define _M_ROOM_H__

#include "util.hpp"
#include "logger.hpp"
#include "online.hpp"
#include "db.hpp"
#include <vector>
#include <memory>
#include <unordered_map>

#define BOARD_ROW 15
#define BOARD_COL 15
#define CHESS_WHITE 1
#define CHESS_BLACK 2

typedef enum{ GAME_START, GAME_OVER}room_statu;

class room
{
    private:
        uint64_t _room_id;// 房间唯一标识符
        room_statu _statu;// 房间状态
        int _player_count; // 玩家数量，决定了什么时候销毁房间
        uint64_t _white_id;// 白棋玩家ID
        uint64_t _black_id;// 黑棋玩家ID
        user_table* _tb_user;// 用户信息表的句柄，输赢结算
        online_manager* _online_user;// 线上用户管理句柄,获取id对应的连接
        std::vector<std::vector<int>> _board;// 棋盘

    private:
        bool five(int row,int col,int row_off,int col_off,int color)
        {
            // row 和 col是下棋的位置，而row_off和col_off是偏移量
            int count = 1;
            int search_row = row + row_off;
            int search_col = col + col_off;
            while(search_row >= 0 && search_row < BOARD_ROW &&
                  search_col >= 0 && search_col < BOARD_COL &&
                  _board[search_row][search_col] == color)
                {
                    count++;
                    search_row += row_off;
                    search_col += col_off;
                }

            // 相反方向再来一次
            search_row = row - row_off;
            search_col = col - col_off;
            while(search_row >= 0 && search_row < BOARD_ROW &&
                  search_col >= 0 && search_col < BOARD_COL &&
                  _board[search_row][search_col] == color)
                {
                    count++;
                    search_row -= row_off;
                    search_col -= col_off;
                }

            return (count >= 5);
        }

        // 判断下了该位置的棋子后是否出现胜利情况
        uint64_t check_win(int row,int col,int color)
        {
            if( five(row,col,0,1,color) ||
                five(row,col,1,0,color) ||
                five(row,col,1,1,color) ||
                five(row,col,1,-1,color))
            {
                return color==CHESS_WHITE?_white_id:_black_id;
            }
            return 0;// 没有赢家
        }

    public:
        using room_ptr = std::shared_ptr<room>;
        room(uint64_t room_id,user_table* tb_user,online_manager* online_user):
            _room_id(room_id),_statu(GAME_START),_player_count(0),
            _tb_user(tb_user),_online_user(online_user),
            _board(BOARD_ROW,std::vector<int>(BOARD_COL,0))
        {
            DLOG("%lu room create success!",_room_id);
        }

        ~room()
        {
            DLOG("%lu room is destroyed!",_room_id);
        }

        uint64_t id(){ return _room_id; }
        room_statu statu(){ return _statu; }
        int player_count(){ return _player_count; }
        void add_white_user(uint64_t uid){ _white_id = uid;_player_count++; }
        void add_black_user(uint64_t uid){ _black_id = uid;_player_count++; }
        uint64_t get_white_user(){ return _white_id; }
        uint64_t get_black_user(){ return _black_id; }

        // 处理下棋动作
        Json::Value handle_chess(Json::Value& req)
        {
            Json::Value json_resp = req;
            int chess_row = req["row"].asInt();
            int chess_col = req["col"].asInt();
            uint64_t cur_uid = req["uid"].asUInt64();
            int cur_color = (cur_uid == _white_id)?CHESS_WHITE:CHESS_BLACK;

            // 1.判断房间中的两个玩家是否都在线，任意一方不在线，判断另一方胜利
            if(_online_user->is_in_game_room(_white_id) == false)
            {
                json_resp["result"] = true;
                json_resp["reason"] = "对方掉线，您获胜！";
                json_resp["winner"] = (Json::UInt64)_black_id;
                return json_resp;
            }
            if(_online_user->is_in_game_room(_black_id) == false)
            {
                json_resp["result"] = true;
                json_resp["reason"] = "对方掉线，您获胜！";
                json_resp["winner"] = (Json::UInt64)_white_id;
                return json_resp;
            }

            // 2.获取走棋位置是否合理（是否已经被占用）
            if(_board[chess_row][chess_col] != 0)
            {
                json_resp["result"] = false;
                json_resp["reason"] = "当前位置已经有棋子了！";
                return json_resp;
            }

            // 3.判断是否有玩家胜利（从当前走的位置判定是否存在五星连珠）
            _board[chess_row][chess_col] = cur_color;
            uint64_t winner_id = check_win(chess_row,chess_col,cur_color);
            if(winner_id != 0)
            {
                json_resp["reason"] = "五星连珠，您获胜！";
            }
            json_resp["result"] = true;
            json_resp["winner"] = (Json::UInt64)winner_id;
            return json_resp;
        }

        // 处理聊天动作
        Json::Value handle_chat(Json::Value& req)
        {
            Json::Value json_resp = req;
            // 检测消息中是否包含敏感词汇
            std::string msg = req["message"].asString();
            size_t pos = msg.find("fuck");
            if(pos != std::string::npos)
            {
                json_resp["result"] = false;
                json_resp["reason"] = "包含敏感词汇，发送失败！";
                return json_resp;
            }
            json_resp["result"] = true;
            return json_resp;
        }

        // 处理玩家退出动作
        void handle_exit(u_int64_t uid)
        {
            // 如果是下棋中途退出，则对方获胜；若已经是下棋结束，则房间玩家数量-1即可
            Json::Value json_resp;
            if(_statu == GAME_START)
            {
                uint64_t winner_id = (Json::UInt64)(uid == _white_id ? _black_id : _white_id);
                json_resp["optype"] = "put_chess";
                json_resp["result"] = true;
                json_resp["reason"] = "对方掉线，您获胜！";
                json_resp["room_id"] = (Json::UInt64)_room_id;
                json_resp["uid"] = (Json::UInt64)uid;
                json_resp["row"] = -1;
                json_resp["col"] = -1;
                json_resp["winner"] = (Json::UInt64)winner_id;
                uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;
                _tb_user->win(winner_id);
                _tb_user->lose(loser_id);
                _statu = GAME_OVER;
                broadcast(json_resp);
            }
            _player_count--;
            return;
        }

        // 总的请求处理函数，在函数内部，区分请求类型，根据不同的请求调用不同的处理函数，得到响应进行广播
        void handle_request(Json::Value &req)
        {
            Json::Value json_resp;
            // 1.当前请求的房间号是否和当前房间匹配
            uint64_t room_id = req["room_id"].asUInt64();
            if(room_id != _room_id)
            {
                json_resp["optype"] = req["optype"].asString();
                json_resp["result"] = false;
                json_resp["reason"] = "房间号不匹配！";
                broadcast(json_resp);
                return;
            }

            // 2.根据不同的请求类型调用不同的处理函数
            if(req["optype"].asString() == "put_chess")
            {
                json_resp = handle_chess(req);
                if(json_resp["winner"].asUInt64() != 0)
                {
                    uint64_t winner_id = json_resp["winner"].asUInt64();
                    uint64_t loser_id = winner_id==_white_id?_black_id:_white_id;
                    _tb_user->win(winner_id);
                    _tb_user->lose(loser_id);
                    _statu = GAME_OVER;
                }
            }
            else if(req["optype"].asString() == "chat")
            {
                json_resp = handle_chat(req);
            }
            else
            {
                json_resp["optype"] = req["optype"].asString();
                json_resp["result"] = false;
                json_resp["reason"] = "未知操作！";
            }
            std::string body;
            json_util::serialize(json_resp,body);
            DLOG("room-broadcast: %s",body.c_str());
            return broadcast(json_resp);
        }

        // 将制定的信息广播给房间中的所有玩家
        void broadcast(Json::Value& rsp)
        {
            std::string body;
            json_util::serialize(rsp,body);

            // 获取房间中的玩家对应的连接，发送序列化后的消息
            wsserver_t::connection_ptr white_conn = _online_user->get_conn_from_room(_white_id);
            if(white_conn.get() != nullptr)
            {
                white_conn->send(body);
            }
            else
            {
                DLOG("room: get white chess player's connection failed!")
            }

            wsserver_t::connection_ptr black_conn = _online_user->get_conn_from_room(_black_id);
            if(black_conn.get() != nullptr)
            {
                black_conn->send(body);
            }
            else
            {
                DLOG("room: get black chess player's connection failed!")
            }
            return;
        }
};

class room_manager
{
    private:
        std::mutex _mutex;
        uint64_t _next_rid;
        user_table* _tb_user;
        online_manager* _online_user;
        std::unordered_map<uint64_t,room::room_ptr> _rooms;// 房间号对应具体房间的智能指针
        std::unordered_map<uint64_t,u_int64_t> _users; // 通过用户ID找到对应的房间号
    public:
        room_manager(user_table *ut, online_manager *om):
            _next_rid(1), _tb_user(ut), _online_user(om) 
        {
            DLOG("room management module initialization completed!");
        }

        ~room_manager() { DLOG("the room management module is destroyed!"); }

        // 为两个用户创建房间
        room::room_ptr create_room(uint64_t uid1, uint64_t uid2)
        {
            // 两个用户在游戏大厅中进行匹配，匹配成功后创建房间
            // 1.因此第一步是判断两个玩家是否都还在游戏大厅中
            if(_online_user->is_in_game_hall(uid1) == false)
            {
                DLOG("The room creation failed because the user:%lu is not in the game hall",uid1);
                return room::room_ptr();
            }
            if(_online_user->is_in_game_hall(uid2) == false)
            {
                DLOG("The room creation failed because the user:%lu is not in the game hall",uid2);
                return room::room_ptr();
            }

            // 2.创建房间，将用户信息添加到房间中
            std::unique_lock<std::mutex> lock(_mutex);
            room::room_ptr rp = std::make_shared<room>(_next_rid,_tb_user,_online_user);
            rp->add_black_user(uid1);
            rp->add_white_user(uid2);
            // 3.将房间信息管理起来
            _rooms.insert(std::make_pair(_next_rid,rp));
            _users.insert(std::make_pair(uid1,_next_rid));
            _users.insert(std::make_pair(uid2,_next_rid));
            _next_rid++;
            // 4.返回房间信息
            return rp;
        }

        // 通过房间ID获取房间信息
        room::room_ptr get_room_by_rid(uint64_t rid)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _rooms.find(rid);
            if(it == _rooms.end())
            {
                return room::room_ptr();
            }
            return it->second;
        }

        // 通过用户ID获取房间信息
        room::room_ptr get_room_by_uid(uint64_t uid)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            //1. 通过用户ID获取房间ID
            auto uit = _users.find(uid);
            if (uit == _users.end()) 
            {
                return room::room_ptr();
            }

            uint64_t rid = uit->second;
            //2. 通过房间ID获取房间信息
            auto rit = _rooms.find(rid);
            if (rit == _rooms.end()) 
            {
                return room::room_ptr();
            }
            return rit->second;
        }

        // 通过房间ID销毁房间
        void remove_room(uint64_t rid)
        {
            //因为房间信息，是通过shared_ptr在_rooms中进行管理，因此只要将shared_ptr从_rooms中移除
            //则shared_ptr计数器==0，外界没有对房间信息进行操作保存的情况下就会释放

            //1. 通过房间ID，获取房间信息,注意这里别加锁,调用的get_room_by_id里有锁
            room::room_ptr rp = get_room_by_rid(rid);
            if (rp.get() == nullptr) 
            {
                return;
            }

            //2. 通过房间信息，获取房间中所有用户的ID
            uint64_t uid1 = rp->get_white_user();
            uint64_t uid2 = rp->get_black_user();

            //3. 移除房间管理中的用户信息
            std::unique_lock<std::mutex> lock(_mutex);
            _users.erase(uid1);
            _users.erase(uid2);

            //4. 移除房间管理信息
            _rooms.erase(rid);
        }

        // 删除房间中指定用户，如果房间中没有用户了，则销毁房间，用户连接断开时被调用
        void remove_room_user(uint64_t uid) 
        {
            room::room_ptr rp = get_room_by_uid(uid);
            if (rp.get() == nullptr) 
            {
                return;
            }

            rp->handle_exit(uid);
            if(rp->player_count() == 0) // 房间中没有玩家了则进行销毁房间
            {
                remove_room(rp->id());
            }
            return;
        }

};


#endif